﻿/*
 *  PassBag is multi-platform software that securely manage/store passwords
 *  Copyright (C) <2009>  <Hugo Rodrigues PEREIRA>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *	
 *  You can find a copy of the GPLv3 at project <root directory>/gpl.txt
 *
 */

using System;
using System.Security.Cryptography;
using System.Text;

namespace PassBag.Logic.Crypt {
    /// <summary>
    /// Class exposing properties and methods to use with symmetric cryptographic algorithm
    /// </summary>
    public class SymetrycAlgoHelper {

        // minimal key length
        // equal to PassBag.Logic.Settings.XmlSettingsManager.Settings.MinPasswordLength
        private const int MINIMAL_KEY_LEN = 5;

        private static readonly Encoding _initialEncoding = Encoding.Default;
        private static readonly HashAlgorithm _initialHashAlgo = new SHA384Managed ();

        private static int _minimumKeyLength = MINIMAL_KEY_LEN;

        private static Encoding _encoding = _initialEncoding;
        private static HashAlgorithm _hashAlgo = _initialHashAlgo;

        public static int MinimumKeyLength {
            get {
                return _minimumKeyLength;
            }
            set {
                if (value < 0)
                    throw new ArgumentOutOfRangeException ("Key reach the minimum size");
                _minimumKeyLength = value;
            }
        }

        public static Encoding Encoding {
            get {
                return _encoding;
            }
            set {
                if (value == null)
                    throw new ArgumentNullException ("Encoding is null");
                _encoding = value;
            }
        }

        public static HashAlgorithm HashAlgo {
            get {
                return _hashAlgo;
            }
            set {
                if (value == null)
                    throw new ArgumentNullException ("HashAlgo is null");
                _hashAlgo = value;
            }
        }

        public static SymmetricAlgorithm Create (string key) {
            return Initialize (SymmetricAlgorithm.Create (), key);
        }

        public static SymmetricAlgorithm Create (string key, Encoding encoding) {
            return Initialize (SymmetricAlgorithm.Create (), key, encoding);
        }

        public static SymmetricAlgorithm Create (byte[] key, byte[] iv) {
            SymmetricAlgorithm cryptAlgo = SymmetricAlgorithm.Create ();
            cryptAlgo.IV = iv;
            cryptAlgo.Key = key;
            return cryptAlgo;
        }


        public static SymmetricAlgorithm InitializeSymmetricAlgorithm
            (SymmetricAlgorithm encryptAlgo, string keyString, Encoding encoding, HashAlgorithm hashAlgo) {

            if (keyString == null)
                throw new ArgumentNullException ("keyString is null");
            if (keyString.Length < MinimumKeyLength)
                throw new ArgumentException (string.Format ("strKey.Length < {0}", MinimumKeyLength), "strKey");

            if (encryptAlgo == null)
                throw new ArgumentException ("cryptAlgo");
            if (hashAlgo == null)
                throw new ArgumentException ("hashAlgo");
            if (encoding == null)
                throw new ArgumentException ("encoding");

            byte[] keyIV = hashAlgo.ComputeHash (encoding.GetBytes (keyString));

            byte[] key = new byte[encryptAlgo.KeySize >> 3]; // x >> 3 means x/8 (bits in bytes conversion)
            byte[] iv = new byte[encryptAlgo.BlockSize >> 3];

            Array.Copy (keyIV, key, key.Length);
            Array.Copy (keyIV, key.Length, iv, 0, iv.Length);

            encryptAlgo.Key = key;
            encryptAlgo.IV = iv;

            return encryptAlgo;
        }

        public static SymmetricAlgorithm Initialize (SymmetricAlgorithm cryptAlgo, string strKey) {
            return InitializeSymmetricAlgorithm (cryptAlgo, strKey, _encoding, _hashAlgo);
        }

        public static SymmetricAlgorithm Initialize (SymmetricAlgorithm cryptAlgo, string strKey, Encoding encoding) {
            return InitializeSymmetricAlgorithm (cryptAlgo, strKey, encoding, _hashAlgo);
        }

        public static string ToHexString (byte[] b) {
            int length = b.Length;
            int length2 = length << 1; //length*2
            StringBuilder sb = new StringBuilder (length2, length2);
            sb.Append (b[length - 1].ToString ("X"));
            for (int i = length - 2; i >= 0; i--) {
                sb.Append (b[i].ToString ("X").PadLeft (2, '0'));
            }
            return sb.ToString ();
        }

        public static byte[] FromHexString (string s) {
            int p = s.Length;
            int odd = p & 1;
            int length = (p >> 1) + odd;
            byte[] b = new byte[length];

            for (int i = 0; i <= length - 2; i++) {
                p -= 2;
                b[i] = byte.Parse (s.Substring (p, 2), System.Globalization.NumberStyles.HexNumber);
            }
            b[length - 1] = byte.Parse (s.Substring (0, p), System.Globalization.NumberStyles.HexNumber);
            return b;
        }
    }
}
